<?php
/*
 * description：
 * author：wh
 * email：
 * createTime：{2021/08/25} {09:12} 
 */

namespace wanghua\general_utility_tools_php\wechat;


use wanghua\general_utility_tools_php\tool\Tools;

/**
 * 微信-获取用户授权
 *
 * 得到用户基本信息，包括openid，具体信息请看代码，有详细列出
 *
 * ！！！【重要】！！！
 * 此类库目前只做了授权和获取用户信息，其它功能如支付，小程序，公众号其它接口可使用第三方类库：
 * composer require zoujingli/wechat-developer
 * zoujingli/wechat-developer技术文档：https://packagist.org/packages/zoujingli/wechat-developer
 *
 * 微信官方文档：https://mp.weixin.qq.com/wiki
商户支付文档：https://pay.weixin.qq.com/wiki/doc/api/index.html
 *
 * Class UserAuth
 * @package wanghua\general_utility_tools_php\wechat
 */
class UserAuth
{
    protected $wechatConfig = [];
    protected $access_token_path = '';//票据存储位置，必须且具有读写权限
    protected $access_token_file_name = '';//存储文件名

    /**
     * 初始化
     * UserAuth constructor.
     * @param array $wechatConfig 微信全局配置，eg：appid等
     * @param string $access_token_path access_token票据存储相对位置
     */
    public function __construct(array $wechatConfig,string $access_token_file_name='')
    {
        $root_path = Tools::get_root_path();
        $this->wechatConfig = $wechatConfig;//全局配置
        $this->access_token_file_name = $access_token_file_name;
        $this->access_token_path = '';//重置
        $this->access_token_path = $root_path.$wechatConfig['access_token_path'];//access_token票据存储位置

        if($this->access_token_file_name){
            $this->access_token_path = $this->access_token_path.'/'.$this->access_token_file_name;
        }else{
            $this->access_token_path = $this->access_token_path.'/access_token.txt';
        }

        if(!file_exists($root_path.$wechatConfig['access_token_path'])){
            mkdir($root_path.$wechatConfig['access_token_path'], 0777, true);
            file_put_contents($this->access_token_path,'');
        }


        $this->checkParams();

    }
    /**
     * desc：检查参数
     * author：wh
     * @throws \Exception
     */
    protected function checkParams(){
        if(empty($this->wechatConfig['appid'])){
            throw new \Exception('请设置appid');
        }
        if(empty($this->wechatConfig['app_secret'])){
            throw new \Exception('请设置app_secret');
        }

    }

    //请求此页面进行授权，微信将数据发送到重定向接口，接口做下一步业务
    //========================================= [3步获取用户信息 start] =========================================
    /**
     * desc：（1）用户同意授权，获取code （2）第二步在Wexinauth.php ->authReturnUrl方法
     * author：wanghua
     * author：wh
     * @param string $authCallback 授权回调方法，必须包含域名（在微信公众号后台配置网页授权域名，公众号后台->设置与开发->公众号设置）
     */
    function usrAuth(string $authCallbackUrl){
        $appid = $this->wechatConfig['appid'];

        //解释：$authCallbackUrl（授权回调，code会在回调url中出现）
        $url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=$appid&redirect_uri=$authCallbackUrl&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
        echo "<html><script>window.location.href='{$url}'</script></html>";
    }

    /**
     * description：用户授权回调url （以下完整步骤）
     *
     * 【注意】：可以考虑将此代码copy到业务控制器中使用，由Wechat.php->usrAuth授权回调请求
     *
     * array(10) {
    ["openid"]=>
    string(28) "o5_r60xfL0speOoDVcAcrfX24wTw"
    ["nickname"]=>
    string(6) "展望"
    ["sex"]=>
    int(1)
    ["language"]=>
    string(5) "zh_CN"
    ["city"]=>
    string(6) "渝中"
    ["province"]=>
    string(6) "重庆"
    ["country"]=>
    string(6) "中国"
    ["headimgurl"]=>
    string(134) "http://thirdwx.qlogo.cn/mmopen/vi_32/99az237H9C2cahYGicowt3gwGd4wGxgnf4ia4T7ZCFicml7u6EvibAFAZ8ibuB6erwtpe5gUI24VlLuiaaP5ic666HdIQ/132"
    ["privilege"]=>
    array(0) {
    }
    ["unionid"]=>
    string(28) "o20U21BMNw_3i-jpGjudYxfud6uE"
    }
     * author：wanghua
     */
    //function authReturnUrl(string $redirectUrl){
    //    $code = input('code');
    //    //$weixin_logic = new WeixinLogic();
    //
    //    //1 获取用户授权code
    //    $data = $this->usrAccessToken($code);
    //
    //    //2 得到 access_token
    //    $access_token = empty($data['access_token'])?$this->getAccessToken():$data['access_token'];
    //    //得到 access_token 之后全局保存 - 等之后扩展
    //    //if($access_token){
    //    //由于access_token拥有较短的有效期，当access_token超时后，可以使用refresh_token进行刷新，
    //    //refresh_token有效期为30天，当refresh_token失效之后，需要用户重新授权。
    //    //  cookie('usr_auth_access_token', $access_token);//网页授权 access_token 全局存储
    //    //}
    //    //3 获取openid 并保存全局使用
    //
    //
    //    $openid = $data['openid'];
    //    cookie('openId', $openid);
    //
    //    //4 通过access_token 拉取用户信息
    //    $url = "https://api.weixin.qq.com/sns/userinfo?access_token={$access_token}&openid={$openid}&lang=zh_CN";
    //    $result = $this->curl_get($url);
    //    //5 存储，以便后续业务使用
    //    session('wx_user_info', json_decode($result['data'], true));
    //    //6 授权完成跳转至首页进行下一步业务
    //    //检查授权之前的url是否存在
    //
    //    $this->redirect($redirectUrl);
    //}
    //========================================= [3步获取用户信息 end] =========================================
    /**
     * 【非基础token】
     * description：（第2步）通过code换取网页授权access_token，
     * 网页授权接口调用凭证,注意：此access_token与基础支持的access_token不同
     * 得到的数据：
    array(6) {
    ["access_token"]=>
    string(89) "10_ckJ3HZCePXha8yKNCaBEqZnemtt_bWc17IlIAwLh1bewR-3ilWybde_OxjevoO7QYOJzeqqiAL71Vv65vtO-zg"
    ["expires_in"]=>
    int(7200)
    ["refresh_token"]=>
    string(89) "10_OHKwmcQPj6UEt6O0hQiXgIqpdaFkGQNgrbB4zfCIjiGG_-bLqEPDXju4cXVH4FvDuhqG_N5h_OCY1LwZNTNU4g"
    ["openid"]=>
    string(28) "o5_r60xfL0speOoDVcAcrfX24wTw"
    ["scope"]=>
    string(15) "snsapi_userinfo"
    ["unionid"]=>
    string(28) "o20U21BMNw_3i-jpGjudYxfud6uE"
    }
     * @param string $code 来自用户授权code
     * author：wanghua
     */
    function usrAccessToken(string $code){
        $appid = $this->wechatConfig['appid'];
        $secret = $this->wechatConfig['app_secret'];
        //获取code后，请求以下链接获取access_token
        $url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid={$appid}&secret={$secret}&code={$code}&grant_type=authorization_code";
        $res_data = $this->curl_get($url);
        $data = json_decode($res_data['data'], true);
        return $data;
    }

    /**
     * desc：小程序授权
     *
     * https://developers.weixin.qq.com/minigame/dev/api-backend/open-api/login/auth.code2Session.html
     *
     * author：wh
     * @param string $code
     * @return {
     * ["session_key"] => string(24) "ubVY3tqzoxNsYCVe9orBDg=="
     * ["openid"] => string(28) "oIHWi61D2nfwNLZsq-5BfDs_qT-A"
     * }
     */
    function usrAccessTokenApplet(string $code){
        $appid = $this->wechatConfig['appid'];
        $secret = $this->wechatConfig['app_secret'];
        //获取code后，请求以下链接获取access_token
        $url = "https://api.weixin.qq.com/sns/jscode2session?appid={$appid}&secret={$secret}&js_code={$code}&grant_type=authorization_code";
        $res_data = $this->curl_get($url);
        $data = json_decode($res_data['data'], true);
        return $data;
    }
    /**
     * description：获取票据
     * author：wanghua
     */
    function getAccessToken(){


        $access_token = file_get_contents($this->access_token_path);
        //获取票据，并验证是否超时，保证票据有效（全局唯一）
        if(!trim($access_token)){
            return $this->getAccessTokenNow();//实时获取
        }else{
            //是否过期
            $exp = explode('==', $access_token);
            if(time() - $exp[1]*1 >= 6900){//允许提前5分钟刷新
                return $this->getAccessTokenNow();//实时获取
            }else{
                return $exp[0];
            }
        }
    }


    /**
     * description：实时获取【基础-非网页授权token】
     * author：wanghua
     */
    function getAccessTokenNow(){
        try{
            //实时获取
            $appid = $this->wechatConfig['appid'];
            $appsecret = $this->wechatConfig['app_secret'];
            $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$appsecret";
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            $output = curl_exec($ch);
            curl_close($ch);
            $jsoninfo = json_decode($output, true);

            file_put_contents($this->access_token_path, $jsoninfo["access_token"].'=='.time());//永久保存
            return $jsoninfo["access_token"];
        }catch (\Exception $e){
            Tools::log_to_write_txt(['error_title'=>'[错误]获取票据出错:'.$e->getMessage(),'error_info'=>$e->getTraceAsString()]);
            return false;
        }
    }


    /**
     * cURL 网络链接库
     * GET
     * author：wh
     * @param $url
     * @return bool|int|string
     */
    function curl_get($url, $timeout = 10)
    {

        $header = array(
            'Accept: application/json',
        );
        $curl = curl_init();
        //curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
        //curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
        //curl_setopt($curl, CURLOPT_SSLVERSION, 3);
        //设置抓取的url
        curl_setopt($curl, CURLOPT_URL, $url);
        //设置头文件的信息作为数据流输出
        curl_setopt($curl, CURLOPT_HEADER, 0);
        // 超时设置,以秒为单位
        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);

        // 超时设置，以毫秒为单位
        // curl_setopt($curl, CURLOPT_TIMEOUT_MS, 500);

        // 设置请求头
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        //设置获取的信息以文件流的形式返回，而不是直接输出。
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
        //执行命令
        $data = curl_exec($curl);

        // 显示错误信息
        if (curl_error($curl)) {
            //print "Error: ".curl_errno($curl).'-' . curl_error($curl);
            //返回错误码
            return ['code' => curl_errno($curl), 'msg' => curl_error($curl)];
        } else {
            //关闭句柄
            curl_close($curl);
            // 返回的内容
            return ['code' => 200, 'msg' => 'ok', 'data' => $data];
        }
    }
}